package org.geektimes.projects.user.oauth2;

import com.alibaba.fastjson.JSONObject;
import okhttp3.*;
import org.apache.commons.lang.StringUtils;

import javax.servlet.*;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

import static org.apache.commons.lang.StringUtils.substringAfter;

/**
* open authentication 2 过滤处理器  
* @author : KangNing Hu
*/
public class OAuth2Handler extends HttpServlet {

	public final static String PATH = "/oauth2/call";

	// config 委托处理器
	private OAuth2ConfigurationDelegate oAuth2ConfigurationDelegate = new OAuth2ConfigurationDelegate();

	// 认证 委托处理器
	private OAuth2AuthenticationDelegate oAuth2AuthenticationDelegate = new OAuth2AuthenticationDelegate();

	//http 客户端调用
	//private OkHttpClient okHttpClient = new OkHttpClient();

	// 认证调用委托器
	private OAtuh2InvokeDelegate oAtuh2InvokeDelegate = new OAtuh2InvokeDelegate();

	// 认证调用实现
	private List<OAtuh2Invoke> invokes = new ArrayList<>();


	public void setConfigs(List<OAuth2Configuration> auth2Configurations){
		auth2Configurations.forEach(config -> oAuth2ConfigurationDelegate.addConfiguration(config));
	}

	public void addOAuth2Authentication(OAuth2Authentication auth2Authentication){
		this.oAuth2AuthenticationDelegate.addAuthentication(auth2Authentication);
	}

	@Override
	public void init(ServletConfig config) throws ServletException {
		super.init(config);
		onInit(config);
		oAuth2ConfigurationDelegate.init();
		invokes.add(new AuthorizeInvoke());
		invokes.add(new OAuth2CallInvoke());
	}

	/**
	 * 用于扩展
	 * @param config servlet config
	 */
	protected void onInit(ServletConfig config) {
		///暂不实现
	}


	@Override
	public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
		if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) {
			//准备配置
			oAuth2ConfigurationDelegate.preConfig((HttpServletRequest) req);
			//进行调用
			oAtuh2InvokeDelegate.invoke((HttpServletRequest) req , (HttpServletResponse) res );
		}
	}


	@Override
	public void destroy() {
		oAuth2ConfigurationDelegate.close();
	}

	/**
	 * oauth2 的调用器
	 */
	public  interface OAtuh2Invoke{
		/**
		 * 是否支持
		 * @param request 当前请求上下文
		 * @return 返回 true 和 false 支持为true
		 */
		boolean support(HttpServletRequest request);

		/**
		 * 进行第三方调用
		 * @param request 当前请求上下文
		 * @param response 当前响应上下文
		 */
		void invoke(HttpServletRequest request , HttpServletResponse response);

		/**
		 * 获取路径
		 * @param request
		 * @return
		 */
		default String getPath(HttpServletRequest request){
			// 建立映射关系
			// requestURI = /a/hello/world
			String requestURI = request.getRequestURI();
			// contextPath  = /a or "/" or ""
			String prefixPath = request.getContextPath();
			// 映射路径（子路径）
			return substringAfter(requestURI, StringUtils.replace(prefixPath, "//", "/"));

		}
	}


	/**
	 * 第三方授权调用
	 */
	private class AuthorizeInvoke implements OAtuh2Invoke{


		private static final String PATH = "/oauth2/authorize";

		@Override
		public boolean support(HttpServletRequest request) {
			return PATH.equals(getPath(request));
		}

		@Override
		public void invoke(HttpServletRequest request, HttpServletResponse response) {
			RedirectUtils.redirect(response , buildUrl(request));
		}

		/**
		 * 构建url
		 * @return 返回构建好的url
		 */
		private String buildUrl(HttpServletRequest request){
			DefaultOAuth2Configuration.ParamConfiguration paramConfiguration = oAuth2ConfigurationDelegate.getParamConfiguration();
			StringBuffer stringBuffer = new StringBuffer()
					.append(oAuth2ConfigurationDelegate.getAuthorizeUrl())
					.append("?")
					//client id
					.append(paramConfiguration.getClientId())
					.append("=")
					.append(oAuth2ConfigurationDelegate.getClientId())
					.append("&")
					// redirect Uri
					.append(paramConfiguration.getRedirectUri())
					.append("=")
					.append(getCallUrl(request))
					.append("&")
					//response type
					.append(paramConfiguration.getResponseType())
					.append("=")
					.append(OAuth2ConfigurationDelegate.RESPONSE_TYPE);
					if (!org.apache.commons.lang3.StringUtils.isEmpty(oAuth2ConfigurationDelegate.getScope())){
						stringBuffer = stringBuffer
								.append("&")
								.append("scope=")
								.append(oAuth2ConfigurationDelegate.getScope());
					}
			return stringBuffer.toString();
		}

		/**
		 * 获取回调地址
		 * @param request
		 * @return
		 */
		private String getCallUrl(HttpServletRequest request){
			String domain = StringUtils.replace(request.getRequestURL().toString() , PATH , "");
			return domain + OAuth2Handler.PATH + "?" +OAuth2ConfigurationDelegate.ORIGIN_NAME + "=" + oAuth2ConfigurationDelegate.getOriginName();
		}


	}

	/**
	 * 获取token
	 */
	private class OAuth2CallInvoke implements OAtuh2Invoke{


		final static String LOCATION = "Location";

		final static String TOKEN = "OAuth2-Token";

		@Override
		public boolean support(HttpServletRequest request) {
			return PATH.equals(getPath(request));
		}

		@Override
		public void invoke(HttpServletRequest request, HttpServletResponse response) {
			if (oAuth2AuthenticationDelegate.support(oAuth2ConfigurationDelegate)){
				try {
					Oauth2Token oauth2Token = oAuth2AuthenticationDelegate.authenticated(oAuth2ConfigurationDelegate , request , response);
					redirect(response , oauth2Token , oAuth2ConfigurationDelegate.getCallSucceedUrl());
				}catch (Exception e){
					String msg = e.getMessage();
					if (e instanceof OAuth2Exception){
						msg += String.format("[%s]" , ((OAuth2Exception) e).getOriginName());
					}
					redirect(response , null , oAuth2ConfigurationDelegate.getCallFailureUrl()+"?msg="+ msg);
				}

			}
		}

		/**
		 * 重定向
		 * @param response
		 * @param url
		 */
		private void redirect(HttpServletResponse response , Oauth2Token token , String url){
			try {
				if (token != null){
					Cookie cookie = new Cookie(TOKEN, token.getAccessToken());
					cookie.setPath("/");
					response.addCookie(cookie);
				}
				RedirectUtils.redirect(response , url);
			}catch (Exception e){

			}

		}

	}


	/**
	 * 认证调用委托器
	 */
	private class OAtuh2InvokeDelegate implements OAtuh2Invoke{


		@Override
		public boolean support(HttpServletRequest request) {
			return true;
		}

		@Override
		public void invoke(HttpServletRequest request, HttpServletResponse response) {
			for (OAtuh2Invoke invoke : invokes){
				if (invoke.support(request)){
					invoke.invoke(request, response);
				}
			}
		}
	}


}
